#include "methods.h"

struct methods t[METHODS] = {   
    { "superpos",     &superpos     },
    { "invert",       &invert       },
    { "flip",         &flip         },
    { "scanlines",    &scanlines    },
    { "burn",         &burn         },
    { "toon",         &toon         },
    { "toonextreme",  &toonextreme  },
    { "pinkify",      &pinkify      },
    { "morered",      &morered      },
    { "moregreen",    &moregreen    },
    { "moreblue",     &moreblue     },
    { "moreblack",    &moreblack    },
    { "lessred",      &lessred      },
    { "lessgreen",    &lessgreen    },
    { "lessblue",     &lessblue     },
    { "lessblack",    &lessblack    },
    { "averagered",   &averagered   },
    { "averagegreen", &averagegreen },
    { "averageblue",  &averageblue  },
    { "edge",         &edge         },
    //{ "gauss",       &gauss        },
    { "none",        NULL           }
};

void superpos(spng *png) {
    png_byte r, g, b;
    /* 235,180,93 for nixie accent */
    int accent_r = 255;
    int accent_g = 128;
    int accent_b = 255;
    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);
            r = ptr[0];
            g = ptr[1];
            b = ptr[2];
            ptr[0] = (b + ((x * 255) % 255) + y) % accent_r;
            ptr[1] = (r + ((x * 255) % 255) + y) % accent_g;
            ptr[2] = (g + ((x * 255) % 255) + y) % accent_b;
        }
    }
}

void invert(spng *png) {
    png_byte r, g, b;
    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);
            r = ptr[0];
            g = ptr[1];
            b = ptr[2];
            ptr[0] = 255 - r;
            ptr[1] = 255 - g;
            ptr[2] = 255 - b;
        }
    }
} 

void flip(spng *png) {
    spng *copy = copypng(png);

    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        png_byte* opp_row = copy->row_pointers[(png->height - 1) - y];
        if (y == png->height - y)
            continue;
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);
            png_byte *ptr2 = &(opp_row[x * png->channels]);
            ptr[0] = ptr2[0];
            ptr[1] = ptr2[1];
            ptr[2] = ptr2[2];
        }
    }

    freepng(copy);
}

void scanlines(spng *png) {
    /* for specific color accentuation */
    float accent_r = .911;
    float accent_g = 1.;
    float accent_b = .911;
    /* effect strength */
    float fx;
    /* scanline size */
    int size = 6;
    png_byte r, g, b;
    for (int y = 0; y < png->height; y++) {
        /* scanline relative pixel pos */
        int pos = y % size;
        /* booleans */
        if (pos >= (size / 2))
            fx = 1.;
        //else if (pos <= 1 || pos >= (size / 2) - 1)
        //    fx = .75;
        else
            fx = .8;

        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);

            r = ptr[0] * (fx * accent_r);
            g = ptr[1] * (fx * accent_g);
            b = ptr[2] * (fx * accent_b);

            ptr[0] = r;
            ptr[1] = g;
            ptr[2] = b;
        }
    }
}

void burn(spng *png) {
    float threshhold = .66;
    float reducer  = .5;
    int biggest;
    png_byte r, g, b;
    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);

            r = ptr[0];
            g = ptr[1];
            b = ptr[2];

            biggest = r > g ? (r > b ? r : b) : (g > b ? g : b);
            if (r / 255. < threshhold && r != biggest)
                r *= reducer;
            if (g / 255. < threshhold && g != biggest)
                g *= reducer;
            if (b / 255. < threshhold && b != biggest)
                b *= reducer;

            ptr[0] = r;
            ptr[1] = g;
            ptr[2] = b;
        }
    }
}

void toonstep(spng *png, int step) {
    png_byte r, g, b;
    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);

            r = ptr[0];
            g = ptr[1];
            b = ptr[2];

            r -= r % step;
            g -= g % step;
            b -= b % step;

            ptr[0] = r;
            ptr[1] = g;
            ptr[2] = b;
        }
    }
}

void toon(spng *png) {
    toonstep(png, 16);
}

void toonextreme(spng *png) {
    toonstep(png, 32);
}

void pinkify(spng *png) {
    png_byte r, g, b;

    float r_to_g     = 0.;
    float b_to_g     = 0.;
    float mean_ratio = 0.;

    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);

            r = ptr[0];
            g = ptr[1];
            b = ptr[2];

            r_to_g = g / (r + 0.000001);
            b_to_g = g / (b + 0.000001);
            mean_ratio = (r_to_g + b_to_g) / 2;

            if (mean_ratio > 1)
                g *= .1;
            else
                g *= mean_ratio;
            b *= .9;
            r *= 1.4;

            ptr[0] = r > 255 ? 255 : r;
            ptr[1] = g < 0 ? 0 : g;
            ptr[2] = b;
        }
    }
}

void more_x(spng *png, int index) {
    // three separate color components
    png_byte c1, c2, c3;
    int o1 = (index + 1) % 3, o2 = (index + 2) % 3;

    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);

            // get three color components
            c1 = ptr[index];
            c2 = ptr[o1];
            c3 = ptr[o2];

            ptr[index] = (c1 * 1.1) > MAX_BRT ? MAX_BRT : (c1 * 1.1);
            ptr[o1] = c2;
            ptr[o2] = c3;
        }
    }
}

void morered(spng *png) {
    more_x(png, 0);
}

void moregreen(spng *png) {
    more_x(png, 1);
}

void moreblue(spng *png) {
    more_x(png, 2);
}

void moreblack(spng *png) {
    // three separate color components
    png_byte c1, c2, c3;

    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);

            // get three color components
            c1 = ptr[0];
            c2 = ptr[1];
            c3 = ptr[2];

            ptr[0] = c1 * .9;
            ptr[1] = c2 * .9;
            ptr[2] = c3 * .9;
        }
    }
}

void less_x(spng *png, int index) {
    // three separate color components
    png_byte c1, c2, c3;
    int o1 = (index + 1) % 3, o2 = (index + 2) % 3;

    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);

            // get three color components
            c1 = ptr[index];
            c2 = ptr[o1];
            c3 = ptr[o2];

            ptr[index] = c1 * .9;
            ptr[o1] = c2;
            ptr[o2] = c3;
        }
    }
}

void lessred(spng *png) {
    less_x(png, 0);
}

void lessgreen(spng *png) {
    less_x(png, 1);
}

void lessblue(spng *png) {
    less_x(png, 2);
}

void lessblack(spng *png) {
    // three separate color components
    png_byte c1, c2, c3;

    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);

            // get three color components
            c1 = ptr[0];
            c2 = ptr[1];
            c3 = ptr[2];

            ptr[0] = (c1 * 1.1) > MAX_BRT ? MAX_BRT : (c1 * 1.1);
            ptr[1] = (c2 * 1.1) > MAX_BRT ? MAX_BRT : (c2 * 1.1);
            ptr[2] = (c3 * 1.1) > MAX_BRT ? MAX_BRT : (c3 * 1.1);
        }
    }
}

void average_x(spng *png, int index) {
    // three separate color components
    png_byte c2, c3;
    int o1 = (index + 1) % 3, o2 = (index + 2) % 3;

    for (int y = 0; y < png->height; y++) {
        png_byte* row = png->row_pointers[y];
        for (int x = 0; x < png->width; x++) {
            png_byte *ptr = &(row[x * png->channels]);

            // get three color components
            c2 = ptr[o1];
            c3 = ptr[o2];

            ptr[index] = (c2 + c3) / 2.;
        }
    }
}

void averagered(spng *png) {
    average_x(png, 0);
}

void averagegreen(spng *png) {
    average_x(png, 1);
}

void averageblue(spng *png) {
    average_x(png, 2);
}

void edge(spng *png) {
    float kernel[9] = {
        -1.0, -1.0, -1.0,
        -1.0,  8.0, -1.0,
        -1.0, -1.0, -1.0
    };
    apply_kernel(png, &kernel, 9);
}

void gauss(spng *png) {
    float kernel[9] = {
        .0625, .125, .0625,
        .125,   4.0, .125,
        .0625, .125, .0625
    };
    apply_kernel(png, &kernel, 9);
}

void apply_method(spng *png, void(*method)(spng*)) {
    (*method)(png);
}

void apply_kernel(spng *png, float (*kernel)[], int kernel_size) {
    spng *copy = copypng(png);

    /* TODO: make all this shit generic lol */
    for (int y = 0; y < png->height; y++) {
        int prev_y = y > 0               ? (y - 1) : 0;
        int next_y = y < png->height - 1 ? (y + 1) : y;
        /* acquire 3 by 3 matrix of pixels */
        png_byte* row[3] = {
            copy->row_pointers[prev_y],
            copy->row_pointers[y],
            copy->row_pointers[next_y]
        };
        /* acquire image row of target buffer */
        png_byte* orig_row = png->row_pointers[y];

        for (int x = 1; x < png->width; x++) {

            int prev_x = x > 0              ? (x - 1) : 0;
            int next_x = x < png->width - 1 ? (x + 1) : x;
            png_byte *ptr[9] = {
                &(row[0][prev_x * png->channels]),
                &(row[0][x      * png->channels]),
                &(row[0][next_x * png->channels]),
                &(row[1][prev_x * png->channels]),
                &(row[1][x      * png->channels]),
                &(row[1][next_x * png->channels]),
                &(row[2][prev_x * png->channels]),
                &(row[2][x      * png->channels]),
                &(row[2][next_x * png->channels])
            };
            /* acquire pixel of target buffer */
            png_byte *orig_ptr = &(orig_row[x * png->channels]);

            /* 0  1  2 */
            /* 3  4  5 */
            /* 6  7  8 */
            /* where 4 represents the local pixel */
            png_byte r[9] = {
                ptr[0][0], ptr[1][0], ptr[2][0],
                ptr[3][0], ptr[4][0], ptr[5][0],
                ptr[6][0], ptr[7][0], ptr[8][0]
            };
            png_byte g[9] = {
                ptr[0][1], ptr[1][1], ptr[2][1],
                ptr[3][1], ptr[4][1], ptr[5][1],
                ptr[6][1], ptr[7][1], ptr[8][1]
            };
            png_byte b[9] = {
                ptr[0][2], ptr[1][2], ptr[2][2],
                ptr[3][2], ptr[4][2], ptr[5][2],
                ptr[6][2], ptr[7][2], ptr[8][2]
            };

            png_byte brt[9];
            /* luminance conversion */
            /* digital ITU BT.601 */
            /* 0.299 R + 0.587 G + 0.114 B */
            for (int i = 0; i < 9; i++)
                brt[i] =
                    .299 * r[i] +
                    .587 * g[i] +
                    .114 * b[i];

            /* TODO: */
            /* obv not every kernel operation assumes luminance input */
            /* values, needs changing to */
            int lum = mult_by_kernel(&brt, kernel, kernel_size);

            orig_ptr[0] = lum;
            orig_ptr[1] = lum;
            orig_ptr[2] = lum;
        }
    }

    freepng(copy);
}

int mult_by_kernel(png_byte (*src)[], float (*kernel)[], int kernel_size) {
    float sum = 0;
    for (int i = 0; i < kernel_size; i++)
        sum += (*src)[i] * (*kernel)[(kernel_size - 1) - i];
    sum = sum < 0. ? 0. : sum;
    return (int)sum;
}
